package be.kdg.bombermanunlimited.server.network;

import java.util.ArrayList;
import java.util.List;

import be.kdg.bombermanunlimited.client.network.listeners.IRingServerListener;

public class Node {

	private int port;
	private String ip;
	private IRingServerListener listener;
	private boolean canAcceptConnections;
	private List<Node> connectedNodes;

	public Node(String ip, int port, IRingServerListener listener) {
		this.ip = ip;
		this.port = port;
		this.listener = listener;
		this.canAcceptConnections = true;
		this.connectedNodes = new ArrayList<Node>();
	}

	public boolean canAcceptConnections() {
		return canAcceptConnections;
	}

	public void canAcceptConnections(boolean canAcceptConnections) {
		this.canAcceptConnections = canAcceptConnections;
	}

	public int getPort() {
		return port;
	}

	public String getIp() {
		return ip;
	}

	public boolean isInRing(Node n) {
		return isInRing(n, new ArrayList<Node>());
	}

	private boolean isInRing(Node n, List<Node> done) {
		if (!done.contains(this)) {
			done.add(this);

			if (n.equals(this)) {
				return true;
			} else {
				boolean found = false;
				for (Node node : n.connectedNodes) {
					found |= node.isInRing(n, done);
				}

				return found;
			}
		} else {
			return false;
		}
	}

	public IRingServerListener getListener() {
		return listener;
	}

	public int getConnectionCount() {
		return connectedNodes.size();
	}

	public void addConnectedNode(Node node) {
		addConnectedNode(node, true);
	}

	private void addConnectedNode(Node node, boolean addOtherDirection) {
		connectedNodes.add(node);
		if (addOtherDirection) {
			node.addConnectedNode(this, false);
		}
	}

	public void removeAllNodes() {
		for (Node n : connectedNodes) {
			n.removeConnectedNode(this, false);
		}
	}

	public boolean removeConnectedNode(Node node) {
		return removeConnectedNode(node, true);
	}

	private boolean removeConnectedNode(Node node, boolean removeOtherDirection) {
		if (connectedNodes.contains(node)) {
			if (removeOtherDirection && node != null) {
				node.removeConnectedNode(this, false);
			}

			return connectedNodes.remove(node);
		} else {
			return false;
		}
	}

	public List<Node> getConnectedNodes() {
		return connectedNodes;
	}

	public List<Node> getNodesInRing() {
		List<Node> list = new ArrayList<Node>();
		list.add(this);
		createRingList(list);

		return list;
	}

	public Node findChildNode(String ip) {
		int index = 0;

		while (index < connectedNodes.size()
				&& !connectedNodes.get(index).getIp().equals(ip)) {
			index++;
		}

		return (index < connectedNodes.size() ? connectedNodes.get(index)
				: null);
	}

	public Node findNode(String ip, int port) {
		return findNode(ip, port, new ArrayList<Node>());
	}
	
	public Node findNode(String ip) {
		return findNode(ip, 0, new ArrayList<Node>());
	}

	private Node findNode(String ip, int port, List<Node> done) {
		if (!done.contains(this)) {
			done.add(this);

			if (this.ip.equals(ip) && this.port == port) {
				return this;
			} else {
				int index = 0;
				Node n = null;
				while (index < connectedNodes.size() && n == null) {
					n = connectedNodes.get(index).findNode(ip, port, done);

					index++;
				}

				return n;
			}
		} else {
			return null;
		}
	}

	private void createRingList(List<Node> list) {
		if (!list.containsAll(connectedNodes)) {
			addToRingList(list, connectedNodes);

			for (Node n : connectedNodes) {
				n.createRingList(list);
			}
		}
	}

	private void addToRingList(List<Node> dest, List<Node> addList) {
		List<Node> uniques = new ArrayList<Node>(addList);
		uniques.removeAll(dest);

		dest.addAll(uniques);
	}

	@Override
	public boolean equals(Object o) {
		if (o.getClass() != this.getClass()) {
			return false;
		} else {
			Node n = (Node) o;

			// the ip and port makes this object unique!
			return n.port == this.port && n.ip.equals(this.ip);
		}
	}

	@Override
	public String toString() {
		String tmp = "Node(" + ip + ":" + port + ", " + canAcceptConnections
				+ ", " + connectedNodes.size() + ")";
		for (Node n : connectedNodes) {
			tmp += "\n" + n.getIp() + ":" + n.getPort();
		}

		tmp += "\n------------";

		return tmp;
	}
}
